package com.cennavi.minenavidemo.utils;

import android.content.Context;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.minedata.minenavi.SDKInitializer;
import com.minedata.minenavi.mapdal.FileSource;
import com.minedata.minenavi.mapdal.Tools;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.FileNameMap;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * OkHttp3工具类
 * Created by lijingru on 2017/9/19.
 */

public class OkHttp3Utils {
    private static Context mContext = SDKInitializer.getContext();
    private static String phoneSign = Tools.getPhoneID(mContext);
    private static OkHttpClient client = null;
    private static HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
    private static Map<String, String> httpHeaders = new HashMap<>();
    private static String userAgentString = getUserAgent(
            String.format("Android_%s_%s",
                    "图吧导航",
                    "9.3.1.3e0b429"));

    /**
     * 构造方法
     */
    private OkHttp3Utils() {
    }

    /**
     * 得到一个OkHttpClient对象
     *
     * @return OkHttpClient对象
     */
    public static OkHttpClient getInstance() {
        if (client == null) {
            synchronized (OkHttp3Utils.class) {
                if (client == null) {
                    try {
                        client = new OkHttpClient.Builder()
                                .connectTimeout(10, TimeUnit.SECONDS)//10 秒连接超时
                                .writeTimeout(10, TimeUnit.SECONDS)//10m秒写入超时
                                .readTimeout(10, TimeUnit.SECONDS)//10秒读取超时
                                .connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS))
                                .cookieJar(new CookieJar() {
                                    @Override
                                    public void saveFromResponse(HttpUrl httpUrl, List<Cookie> cookies) {
                                        cookieStore.put(httpUrl.host(), cookies);
                                    }

                                    @Override
                                    public List<Cookie> loadForRequest(HttpUrl httpUrl) {
                                        List<Cookie> cookies = new ArrayList<>();
                                        Cookie cookie = new Cookie.Builder()
                                                .hostOnlyDomain(httpUrl.host())
                                                .name("m_guid").value(phoneSign)
                                                .build();
                                        cookies.add(cookie);
                                        return cookies;
                                    }
                                })
                                //添加SSL证书验证
                                .sslSocketFactory(new CustomSSLSocketFactory(new TrustManager[]{new MyX509TrustManager()}), new MyX509TrustManager())
                                //添加服务器主机名称验证
                                .hostnameVerifier(new MyHostnameVerifier())
                                .build();
                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    } catch (KeyManagementException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return client;
    }

    /**
     * 添加全局的网络请求头信息，比如Cookie等
     *
     * @param key   自定义的key
     * @param value key对应的值
     */
    public static void addGlobalHttpHeader(String key, String value) {
        httpHeaders.put(key, value);
    }

    /**
     * Get请求
     *
     * @param url      url
     * @param callback 回调
     */
    public static void doGet(String url, Callback callback) {
        Request request = new Request.Builder()
                .url(url)
                .build();
        Call call = getInstance().newCall(request);
        call.enqueue(callback);
    }

    /**
     * Get请求 同步
     *
     * @param url url
     */
    public static Response doGetT(String url) throws IOException {
        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append(url);

        Request.Builder builder = new Request.Builder()
                .url(url);

        for (String key : httpHeaders.keySet()) {
            builder.addHeader(key, httpHeaders.get(key));
        }

        Request request = builder.build();
        return getInstance().newCall(request).execute();
    }

    /**
     * Post请求发送键值对数据
     *
     * @param url       url
     * @param mapParams 键值对数据
     * @param callback  回调
     */
    public static void doPost(String url, Map<String, Object> mapParams, Callback callback) {
        FormBody.Builder builder = new FormBody.Builder();

        for (String key : mapParams.keySet()) {
            builder.add(key, mapParams.get(key) + "");
        }

        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        Call call = getInstance().newCall(request);
        call.enqueue(callback);
    }

    /**
     * Post请求发送键值对数据 同步
     *
     * @param url       url
     * @param mapParams 键值对数据
     */
    public static Response doPostT(String url, Map<String, Object> mapParams) throws IOException {
        FormBody.Builder builder = new FormBody.Builder();
        for (String key : mapParams.keySet()) {
            builder.add(key, mapParams.get(key) + "");
        }
        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        return getInstance().newCall(request).execute();
    }

    /**
     * Post请求发送键值对数据 同步
     *
     * @param url       url
     * @param mapParams 键值对数据
     * @param userAgent 用户代理
     */
    public static Response doPostT(String url, Map<String, Object> mapParams, String userAgent) throws IOException {
        FormBody.Builder builder = new FormBody.Builder();

        for (String key : mapParams.keySet()) {
            builder.add(key, mapParams.get(key) + "");
        }

        StringBuilder urlBuilder = new StringBuilder();
        urlBuilder.append(url);

        if (!TextUtils.isEmpty(SDKInitializer.getApiKey())) {
            urlBuilder.append("?appKey=");
            urlBuilder.append(SDKInitializer.getApiKey());
        }

        Request.Builder requestBuilder = new Request.Builder()
                .url(urlBuilder.toString())
                .addHeader("User-Agent", userAgent)
                .addHeader("mck", "m_guid=" + phoneSign)
                .post(builder.build());

        for (String key : httpHeaders.keySet()) {
            requestBuilder.addHeader(key, httpHeaders.get(key));
        }

        Request request = requestBuilder.build();
        return getInstance().newCall(request).execute();
    }

    /**
     * Post请求发送JSON数据
     *
     * @param url        url
     * @param jsonParams JSON数据
     * @param callback   回调
     */
    public static void doPost(String url, String jsonParams, Callback callback) {
        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8")
                , jsonParams);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Call call = getInstance().newCall(request);
        call.enqueue(callback);
    }

    /**
     * Post请求发送JSON数据 同步
     *
     * @param url        url
     * @param jsonParams JSON数据
     */
    public static Response doPostT(String url, String jsonParams) throws IOException {
        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8")
                , jsonParams);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        return getInstance().newCall(request).execute();
    }

    /**
     * 上传文件
     *
     * @param url       url
     * @param pathName  路径名
     * @param fileName  文件名
     * @param fileType  文件类型
     * @param mapParams 键值对
     * @param callback  回调
     */
    public static void doFile(String url, String pathName, String fileName, String fileType, Map<String, Object> mapParams, Callback callback) {
        MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpg");

        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        if (mapParams == null) {
            builder.addFormDataPart(fileType, fileName,
                    RequestBody.create(MEDIA_TYPE_PNG, new File(pathName)));

        } else {
            for (String key : mapParams.keySet()) {
                builder.addFormDataPart(key, mapParams.get(key) + "");
            }

            builder.addFormDataPart(fileType, fileName,
                    RequestBody.create(MEDIA_TYPE_PNG, new File(pathName)));

        }

        //发出请求参数
        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        Call call = getInstance().newCall(request);
        call.enqueue(callback);
    }

    /**
     * 上传文件同步
     *
     * @param url       url
     * @param pathName  路径名
     * @param fileName  文件名
     * @param fileType  文件类型
     * @param mapParams 键值对
     */
    public static Response doFileT(String url, String pathName, String fileName, String fileType, Map<String, Object> mapParams) throws IOException {
        MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpg");

        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        if (mapParams == null) {
            builder.addFormDataPart(fileType, fileName,
                    RequestBody.create(MEDIA_TYPE_PNG, new File(pathName)));

        } else {
            for (String key : mapParams.keySet()) {
                builder.addFormDataPart(key, mapParams.get(key) + "");
            }

            builder.addFormDataPart(fileType, fileName,
                    RequestBody.create(MEDIA_TYPE_PNG, new File(pathName)));

        }

        //发出请求参数
        Request request = new Request.Builder()
                .url(url)
                .post(builder.build())
                .build();
        return getInstance().newCall(request).execute();
    }

    /**
     * 根据文件路径判断MediaType
     *
     * @param path 文件路径
     * @return 文件类型
     */
    private static String judgeType(String path) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        String contentTypeFor = fileNameMap.getContentTypeFor(path);
        if (contentTypeFor == null) {
            contentTypeFor = "application/octet-stream";
        }
        return contentTypeFor;
    }

    /**
     * 下载文件
     *
     * @param url      url
     * @param fileDir  文件夹名
     * @param fileName 文件名
     */
    public static void downFile(String url, final String fileDir, final String fileName) {
        Request request = new Request.Builder()
                .url(url)
                .build();
        Call call = getInstance().newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                InputStream is = null;
                byte[] buf = new byte[2048];
                int len = 0;
                FileOutputStream fos = null;
                try {
                    is = response.body().byteStream();
                    File file = new File(fileDir, fileName);
                    fos = new FileOutputStream(file);
                    //---增加的代码---
                    //TODO 计算进度
                    long totalSize = response.body().contentLength();
                    long sum = 0;
                    while ((len = is.read(buf)) != -1) {
                        sum += len;
                        //progress就是进度值
                        int progress = (int) (sum * 1.0f / totalSize * 100);
                        //---增加的代码---
                        fos.write(buf, 0, len);
                    }
                    fos.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (is != null) is.close();
                    if (fos != null) fos.close();
                }
            }
        });
    }

    /**
     * 通用方法  map post
     *
     * @param url
     * @param mapParams
     * @return
     */
    @Nullable
    public static String getOkHttpString(String url, Map<String, Object> mapParams) {
        String result = null;
        boolean is_ret = false;
        int tryCount = 0;

        while (!is_ret) {
            Response response = null;

            try {
                response = doPostT(url, mapParams, userAgentString);
                is_ret = response.isSuccessful();

                if (is_ret) {
                    result = response.body().string();
                } else {
                    if (!TextUtils.isEmpty(response.message())) {
                        result = "{\"status\":" + response.code() + ",\"message\":" + "\"" + response.message() + "\"" + "}";
                    } else {
                        result = "{\"status\":" + response.code() + ",\"message\":\"\"" + "}";
                    }
                }

                tryCount++;

                if (tryCount > 3)
                    break;
            } catch (IOException e) {
                e.printStackTrace();
                tryCount++;

                if (tryCount > 3)
                    break;
            }

            if (response != null) {
                if (response.body() != null)
                    response.body().close();
                response.close();
            }
        }

        return result;
    }

    /**
     * 通用方法  map get
     *
     * @param url
     * @return
     */
    @Nullable
    public static String getOkHttpString(String url) {
        String result = null;
        String resultStr = null;
        boolean is_ret = false;
        int tryCount = 0;

        while (!is_ret) {
            Response response = null;

            try {
                response = doGetT(url);

                if (response.isSuccessful()) {
                    resultStr = response.body().string();
                }

                is_ret = response.isSuccessful();
                tryCount++;

                if (tryCount > 3)
                    break;
            } catch (IOException e) {
                e.printStackTrace();
                tryCount++;

                if (tryCount > 3)
                    break;
            }

            if (response != null) {
                if (response.body() != null)
                    response.body().close();
                response.close();
            }
        }

        if (is_ret) {
            result = resultStr;
        }

        return result;
    }

    @NonNull
    private static String getUserAgent(String s) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0, length = s.length(); i < length; i++) {
            char c = s.charAt(i);
            if (c <= '\u001f' || c >= '\u007f') {
                sb.append(String.format("\\u%04x", (int) c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    private static String[] listFiles(Context context, String folder, String fileRegex) throws IOException {
        List<String> ret = new ArrayList<>();
        String[] list = context.getAssets().list(folder);
        for (String asset : list) {
            if (asset.matches(fileRegex)) {
                ret.add(asset);
            }
        }
        return ret.toArray(new String[0]);
    }

    private static class CustomSSLSocketFactory extends SSLSocketFactory {

        private SSLSocketFactory sslSocketFactory;

        public CustomSSLSocketFactory(TrustManager[] trustManager) throws KeyManagementException, NoSuchAlgorithmException {
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, trustManager, new SecureRandom());
            sslSocketFactory = context.getSocketFactory();
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return sslSocketFactory.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return sslSocketFactory.getSupportedCipherSuites();
        }

        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket(s, host, port, autoClose));
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket(host, port));
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket(host, port, localHost, localPort));
        }

        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket(host, port));
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket(address, port, localAddress, localPort));
        }

        @Override
        public Socket createSocket() throws IOException {
            return enableTLSOnSocket(sslSocketFactory.createSocket());
        }

        private Socket enableTLSOnSocket(Socket socket) {
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.1"});
            return socket;
        }
    }

    private static class MyX509TrustManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
            if (certs == null) {
                throw new CertificateException("X509Certificate array is null.");
            }
            if (certs.length < 1) {
                throw new CertificateException("X509Certificate is empty.");
            }
            if (!(null != authType && authType.equals("ECDHE_RSA"))) {
                throw new CertificateException("AuthType is not ECDHE_RSA.");
            }

            //检查所有证书
            try {
                TrustManagerFactory factory = TrustManagerFactory.getInstance("X509");
                factory.init((KeyStore) null);
                for (TrustManager trustManager : factory.getTrustManagers()) {
                    ((X509TrustManager) trustManager).checkServerTrusted(certs, authType);
                }
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            }

            //获取本地证书中的信息
            String clientEncoded = "";
            String clientSubject = "";
            String clientIssUser = "";

            try {
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                final String[] fileList = listFiles(mContext, "certificate", "(?i).*\\.(cer|crt)");

                if (fileList == null || fileList.length == 0) {
                    return;// 暂不做验证
//          throw new CertificateException("Lack of client's certificate.");
                }

                InputStream inputStream = mContext.getAssets().open("certificate/" + fileList[0]);
                X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                clientEncoded = new BigInteger(1, clientCertificate.getPublicKey().getEncoded()).toString(16);
                clientSubject = clientCertificate.getSubjectDN().getName();
                clientIssUser = clientCertificate.getIssuerDN().getName();
            } catch (IOException e) {
                e.printStackTrace();
            }

            boolean valid = false;

            //获取网络中的证书信息
            for (X509Certificate cert : certs) {
                PublicKey publicKey = cert.getPublicKey();
                String serverEncoded = new BigInteger(1, publicKey.getEncoded()).toString(16);
                String subject = cert.getSubjectDN().getName();
                String issuser = cert.getIssuerDN().getName();

                if (clientEncoded.equals(serverEncoded) && clientSubject.equals(subject) && clientIssUser.equals(issuser)) {
                    valid = true;
                    break;
                }
            }

            if (!valid) {
                throw new CertificateException("Server's certificate is not equals to client's certificate.");
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    private static class MyHostnameVerifier implements HostnameVerifier {

        @Override
        public boolean verify(String hostname, SSLSession session) {
            Map<String, String> map = FileSource.getInstance(mContext).loadFileFromAsset("config.ini");

            if (map == null || map.isEmpty()) {
                return true;
            }

            String searchHostname = map.get("search_hostname");
            String inverseHostname = map.get("inverse_hostname");

            if (TextUtils.isEmpty(searchHostname) && TextUtils.isEmpty(inverseHostname)) {
                return true;
            }

            if (!TextUtils.isEmpty(searchHostname) && hostname.equals(searchHostname)) {
                return true;
            }

            if (!TextUtils.isEmpty(inverseHostname) && hostname.equals(inverseHostname)) {
                return true;
            }

            return false;
        }
    }

    /***********使用例子**********/

    protected void test() {
        /*
         * Get请求
         * 参数一：请求Url
         * 参数二：请求回调
         */
        OkHttp3Utils.doGet("https://www.so.com/", new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
//                    tv.setText(response.body().string());
                }
                //关闭防止内存泄漏
                if (response.body() != null) {
                    response.body().close();
                }
            }
        });

        /*
         * Post请求
         * 参数一：请求Url
         * 参数二：请求的键值对
         * 参数三：请求回调
         */
        Map<String, Object> map = new HashMap<>();
        map.put("username", "Hensen");
        map.put("password", "123456");

        OkHttp3Utils.doPost("http://192.168.31.109:8080/test.php", map, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

        /*
         * Post请求
         * 参数一：请求Url
         * 参数二：请求的JSON
         * 参数三：请求回调
         */
        String json = "{\"name\":\"Hensen\"}";

        OkHttp3Utils.doPost("http://192.168.0.8:8080/test.php", json, new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

        /*
         * 上传文件
         * 参数一：请求Url
         * 参数二：保存文件的路径名
         * 参数三：保存文件的文件名
         * 参数四：键值对
         * 参数四：请求回调
         */
//        OkHttp3Utils.doFile("http://192.168.0.8:8080/WD/admin.php?a=ajaxuploadimage&&c=Image",
//                "/data/data/com.handsome.app4/logoa.jpg", "logoa.jpg",null, new Callback() {
//                    @Override
//                    public void onFailure(Call call, IOException e) {
//
//                    }
//
//                    @Override
//                    public void onResponse(Call call, Response response) throws IOException {
//                        response.body().string();
//                    }
//                });

        /*
         * 下载文件
         * 参数一：请求Url
         * 参数二：保存文件的路径名
         * 参数三：保存文件的文件名
         */
        OkHttp3Utils.downFile("http://shouji.360tpcdn.com/160804/a05b75b7779f7a4afae83601c195ed2b" +
                        "/com.qihoo.haosou_708.apk"
                , "/data/data/com.handsome.app4/", "aqy.apk");

    }
}
